<?php
/*
 * Copyright 2010 Johannes M. Schmitt
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

abstract class JavaCompiler 
{
  protected $commandLineFlags;
  protected $availableCommandLineFlags;
  protected $arguments;
  protected $executed;
  protected $error;
  protected $output;
  
  public function __construct()
  {
    $this->initialize();
  }
  
  protected function initialize()
  {
    $this->commandLineFlags = array();
    $this->availableCommandLineFlags = array();
    $this->arguments = array();
    $this->executed = false;
    
    $this->setup();
    $this->configure();
  }
  
  protected function setup() 
  {
  
  }
  
  protected function configure() 
  {
  
  }
  
  abstract public function getJarPath();
  
  public function addArgument($value)
  {
    $this->arguments[] = $value;
  }
  
  protected function addCommandLineFlag($flag)
  {
    $this->availableCommandLineFlags[] = $flag;
  }
  
  /**
   * Sets a command line flag
   * 
   * @param string $flag
   * @param mixed $value if an array is passed, the array overwrites all existing
   * 										 values
   * @return JavaCompiler
   */
  public function setCommandLineFlag($flag, $value = null) 
  {
    if (count($this->availableCommandLineFlags) > 0 && !in_array($flag, $this->availableCommandLineFlags, true))
      throw new InvalidArgumentException(sprintf('The command-line flag "%s" does not exist.', $flag));
      
    if (is_array($value))
    	$this->commandLineFlags[$flag] = $value;
    else
    	$this->commandLineFlags[$flag][] = $value;
    	
    return $this;
  }
  
  public function hasCommandLineFlag($flag)
  {
  	return isset($this->commandLineFlags[$flag]) && count($this->commandLineFlags[$flag]) > 0;
  }
  
  public function getCommandLineFlag($flag, $default = array())
  {
  	return isset($this->commandLineFlags[$flag])?
  			$this->commandLineFlags[$flag] : $default;
  }
  
  public function execute()
  {
    if ($this->executed)
      return;
    $this->executed = true;
    
    $cmd = $this->getCommand();
    $h = proc_open($cmd, $descriptorspec = array(
       0 => array("pipe", "r"),  // stdin is a pipe that the child will read from
       1 => array("pipe", "w"),  // stdout is a pipe that the child will write to
       2 => array("pipe", "w") // stderr is a file to write to
    ), $pipes);

    fclose($pipes[0]);
    
    $error = '';
    while (!feof($pipes[2]))
      $error .= fgets($pipes[2]);
    fclose($pipes[2]);
      
    $output = '';
    while (!feof($pipes[1]))
      $output .= fgets($pipes[1]);
    fclose($pipes[1]);
    
    $this->error = $error;
    $this->output = $output;
  }
  
  public function getError()
  {
    return $this->error;
  }
  
  public function getOutput()
  {
    return $this->output;
  }
  
  public function getCommand()
  {
    $cmd = escapeshellarg(self::getJavaCli()).' -jar '.escapeshellarg($this->getJarPath());
    
    foreach ($this->commandLineFlags as $flag => $values)
    {
      if (!is_array($values))
        $values = array($values);
        
      foreach ($values as $value)
      {
        $cmd .= ' --'.$flag;
      
        if ($value === null)
          continue;
		else if (is_bool($value))
			$cmd .= ' '.($value? 'true' : 'false');
        else          
        	$cmd .= ' '.escapeshellarg($value);
      }
    }
    
    foreach ($this->arguments as $arg)
    {
      if (sfToolkit::isPathAbsolute($arg))
        $arg = str_replace('\\', '/', $arg);
      $cmd .= ' '.escapeshellarg($arg);
    }
    
    // fixes a seemingly broken C implementation of the proc_open command
    // see: http://coding.derkeiler.com/Archive/PHP/comp.lang.php/2005-01/1724.html
    if (stripos(PHP_OS, 'win') === 0)
      $cmd = 'cmd /C "'.$cmd.'"'; 

    return $cmd;
  }
  
  public static function getJavaCli()
  {
      $path = getenv('PATH') ? getenv('PATH') : getenv('Path');
      $suffixes = DIRECTORY_SEPARATOR == '\\' ? (getenv('PATHEXT') ? explode(PATH_SEPARATOR, getenv('PATHEXT')) : array('.exe', '.bat', '.cmd', '.com')) : array('');
      foreach (array('java') as $cli)
      {
        foreach ($suffixes as $suffix)
        {
          foreach (explode(PATH_SEPARATOR, $path) as $dir)
          {
            if (is_file($file = $dir.DIRECTORY_SEPARATOR.$cli.$suffix) && is_executable($file))
            {
              return $file;
            }
          }
        }
      }
  
      throw new RuntimeException('Unable to find Java executable.');
  }
  
  public function hasError()
  {
    return $this->error != '';
  }
}